package archer.back.controllers

import java.io.{ ByteArrayInputStream, IOException }
import java.text.{ ParseException, SimpleDateFormat }

import archer.back.models._
import utils.{ ArcherHelper, CommonDB }
import io.circe.syntax._
import models.TableData

import scala.concurrent.{ ExecutionContext, Future }
import cats.implicits._
import org.jsoup.Jsoup
import play.api.libs.ws.WSClient

import scala.util.{ Failure, Success, Try }
import scala.collection.JavaConverters._

class NodeInfoService(commonDB: CommonDB, wsClient: WSClient)(
  implicit
  ec: ExecutionContext)
  extends ArcherHelper {

  import models.CrawlTables._
  import models.CrawlTables.profile.api._

  def fetch(treeNodeId: Int): Future[Option[NodeInfoWrap]] = {
    val action =
      UpdateTreeNode.filter(_.id === treeNodeId).result.headOption.flatMap {
        case Some(tn) =>
          val isParentAction =
            UpdateTreeNode.filter(_.parentId === tn.id).exists.result
          val nodeInfoOptAction =
            NodeInfo.filter(_.treeNodeId === tn.id).result.headOption
          for {
            isParent <- isParentAction
            infoOpt <- nodeInfoOptAction
            sInfoOpt = infoOpt.map(
              s =>
                SimpleNodeInfo(
                  id = s.id,
                  url = s.url,
                  delayDays = s.delayHours / 24,
                  titleSelector = s.titleSelector,
                  timeSelector = s.timeSelector,
                  timeFormatter = s.timeFormatter,
                  treeNodeId = s.treeNodeId))
          } yield {
            Option(
              NodeInfoWrap(treeNode = tn, info = sInfoOpt, isParent = isParent))
          }
        case _ =>
          DBIO.successful(Option.empty)
      }
    commonDB.db.run(action)
  }

  def testSelector(
    url: String,
    timeSelector: String,
    titleSelector: String,
    timeFormatter: String): Future[TableData[Option[List[NewsInfo]]]] = {
    Try {
      val formatter = new SimpleDateFormat(timeFormatter)
      wsClient
        .url(url)
        .get
        .map { response =>
          Try {
            val chars = response.bodyAsBytes
            val is = new ByteArrayInputStream(chars.toArray)
            val doc = Jsoup.parse(is, null: String, url)
            val titles = doc.select(titleSelector).asScala.toList.map(_.text)
            val timeStrs = doc.select(timeSelector).asScala.toList.map(_.text)
            Try {
              val infos = titles.zip(timeStrs).map {
                case (eachTitle, eachTimeStr) =>
                  val date = formatter.parse(eachTimeStr)
                  NewsInfo(title = eachTitle, updateTime = date)
              }
              TableData(data = Option(infos))
            }.recover {
              case e: ParseException =>
                TableData(
                  data = Option.empty[List[NewsInfo]],
                  msg = s"不可识别的时间：${timeStrs.mkString("<br />", "<br />", "")}")
            }
          }.recover {
            case e: IOException =>
              Try {
                TableData(
                  data = Option.empty[List[NewsInfo]],
                  msg = "不可识别的 Html")
              }
          }.flatten
        }
        .transform { s =>
          s.flatten match {
            case s @ Success(_) =>
              s
            case Failure(_) =>
              Try {
                TableData(data = Option.empty[List[NewsInfo]], msg = "超时或未知异常")
              }
          }
        }
    }.getOrElse(Future.successful(
      TableData(data = Option.empty[List[NewsInfo]], msg = "不可识别的链接地址和时间格式")))
  }

  def create(sInfo: SimpleNodeInfo): Future[Boolean] = {
    commonDB.db
      .run(NodeInfo.+=(sInfo.toDBModel).transactionally)
      .map((_: Int) => true)
  }

  def update(sInfo: SimpleNodeInfo): Future[Boolean] = {
    val model = sInfo.toDBModel
    commonDB.db
      .run(NodeInfo.filter(_.id === model.id).update(model).transactionally)
      .map((_: Int) => true)
  }

}
